// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using CIP4.Common;
using CIP4.Item;

namespace CIP4.Serializer
{
    public class JsonSerializerBuilderType : SerializerBuilderBaseType
    {
        // {0} - namespace
        // {1} - SetProperties invocations
        private const string CSharpSerializerBaseTypeLiteral =
            @"
    public class {0}_SerializerBaseType : SerializerBaseType
    {{
        public static object DeSerializeFromJson(string typeName, BsonValue value)
        {{
            switch (typeName)
            {{
{1}
                default:
                    return ValueFromObject(GetTypeForName(typeName), value);
            }}
        }}

        public static BsonValue SerializeToJson(string typeName, object value)
        {{
            switch (typeName)
            {{
{2}
                default:
                    return BsonValueFromObject(typeName, value);
            }}
        }}
    }}

";

        public static string GetSerializerBaseType()
        {
            return string.Format(CSharpSerializerBaseTypeLiteral,
                FirstNamespaceName,
                DeSerializerBaseTypeBody, 
                SerializerBaseTypeBody);
        }

        // {0} - qualified type name
        private const string CSharpDeSerializerBaseTypeBodyCaseLiteral =
            @"
                case ""{0}"":
                    // this had better be a value and it had better not have an externalId - at least 
                    // a Dont serialize externalid as there will be no where to get it from hence the null docData parameter
                    return {1}.SetProperties(DateTime.Now, null, (BsonDocument)value, null, null);
";

        // {0} - qualified type name
        private const string CSharpSerializerBaseTypeBodyCaseLiteral =
            @"
                case ""{0}"":
                    return {1}.SerializeValue(({2})value, null, null, null);
";

        public static void SerializerBaseTypeBodyCase(TypeContextType theType)
        {
            DeSerializerBaseTypeBody += string.Format(CSharpDeSerializerBaseTypeBodyCaseLiteral,
                theType.TheTypeName,
                theType.TypeSerializerName);
            SerializerBaseTypeBody += string.Format(CSharpSerializerBaseTypeBodyCaseLiteral,
                theType.TheTypeName,
                theType.TypeSerializerName,
                theType.TheTypeFullName);
        }

        public static string FirstNamespaceName { get; set; }

        public static string DeSerializerBaseTypeBody { get; set; }

        public static string SerializerBaseTypeBody { get; set; }

        private readonly LoggerDelegate _logger;

        public JsonSerializerBuilderType(TypeContextType aType, LoggerDelegate logger)
        {
            _logger = logger;
            TheType = aType;
            UsedTypes = new Dictionary<string, TypeContextType>();
        }

        public string GetSerializer(Dictionary<string, TypeContextType> alreadyDone)
        {
            try
            {
                if (alreadyDone.ContainsKey(TheType.TheTypeFullName) || TheType.TheTypeName.EndsWith("<>"))
                    return string.Empty;
                Globals.AddAssemblyPath(Path.GetDirectoryName(TheType.TheType.Assembly.Location), Globals.DefaultLogger);
                return SerializeHeader(alreadyDone);
            }
            finally
            {
                Globals.RemoveAssemblyPath(Path.GetDirectoryName(TheType.TheType.Assembly.Location));
            }
        }

        private const string CSharpNewItemLiteral =
            // {1} - deserialize property assignment list
            // {0} - Type full name (namespace.type)
            // {2} - set private properties (horrible, uses reflection, makes a mockery of encapsulation etc.)
            // {3} - extract externalid if there is one
            @"
            {3}
            {0} objectValue = new {0}();
{1} 
{2}
";

        private const string CSharpExtactExternalIDLiteral =
            @"
            ExternalIDType id = ExternalIDType.ExternalIDFromEscapedString(GetStringValueFromDoc(""_id"", docData));";

        private const string CSharpHeaderItemLiteral =
            // {0} - type name
            // {1} - namespace name
            // {2} - deserialize assignment list
            // {3} - serialize assignment list
            // {4} - publishedID assignment (currently not used...)
            // {5} - ExternalID assignment
            // {6} - serialize stream handling code
            // {7} - DeserializeSubtypes text
            // {8} - item SerializeSubtypes text
            // {9} - value SerializeSubtypes text
            // {10} - newItem text - if the class is abstract, defines objectValue as "object objectValue = null;"
            // {11} - serializer name - either foo_Serializer or foo_Serializer<aType> (if it's generic)
            // {12} - externalid functions
            // {13} - GetValueForPath function
            // {14} - key list - this is getting out of hand...
            // (15) - CheckVersion function
            // (16) - version handling
            // {17} - deserialize version property 
            // {18} - GetItemVersion body
            // {19} - Equality comaparer
            // {20} - first namespace name
            // {21} - IncrementVersionNumber
            // {22} - SerializeAs
            // {23} - GetSerializeAsName
@"
    public class {11} : {20}_SerializerBaseType
    {{
{15}
{14}
{13}
{12}
{23}
        public static {1}.{0} DeSerialize(DateTime versionTimestamp, object docDataObject, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath)
        {{
            return DeSerialize(versionTimestamp, docDataObject, StreamSeeds, propertyPath, null);
        }}
        public static {1}.{0} DeSerialize(DateTime versionTimestamp, object docDataObject, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, {1}.{0} defaultValue)
        {{
            BsonDocument docData = GetDocumentData(docDataObject, typeof({1}.{0}));


            if (docData == null || docData.ElementCount == 0)
                return defaultValue;
{7}
            BsonDocument propDoc = (BsonDocument)docData.First(kvp=> kvp.Name == ""{22}"").Value;
            propertyPath = propertyPath + (string.IsNullOrEmpty(propertyPath) ? string.Empty : ""."") + ""{22}"";
            {1}.{0} objectValue = SetProperties(versionTimestamp, docData, propDoc, StreamSeeds, propertyPath);
{6}    
{16}
            return ({1}.{0})objectValue;
        }}

        public static {1}.{0} SetProperties(DateTime versionTimestamp, BsonDocument docData, BsonDocument propDoc, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath)
        {{
{10}
            return ({1}.{0})objectValue;
        }}

        public static {1}.{0} GetItemVersion(long versionTimestamp, {1}.{0} previousVersion)
        {{
{18}
        }}

        public static {1}.{0} GetItemVersion(long versionTimestamp, {1}.{0}  previousVersion, Dictionary<string, object> Values)
        {{
{17}
            return ({1}.{0})objectValue;
        }}


        internal static List<{1}.{0}> DeSerializeList(DateTime versionTimestamp, object values, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, List<{1}.{0}> defaultValue)
        {{
            if (values == null)
                return defaultValue;
            List<{1}.{0}> list = new List<{1}.{0}>();
            int index = 0;
            foreach (object value in ((BsonArray)values))
                list.Add(DeSerialize(versionTimestamp, (BsonDocument)value, StreamSeeds, propertyPath + ""["" + (index++).ToString() + ""]""));
            return list;
        }}

        public static System.Collections.IDictionary DeSerializeDictionary(DateTime versionTimestamp, object values, GetDictionaryDelegate getDictionary, GetKeyDelegate getKey, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, System.Collections.IDictionary defaultValue)
        {{
            if (values == null)
                return defaultValue;
            System.Collections.IDictionary dictionary = getDictionary();
            BsonDocument valuesDict = (BsonDocument)values;
            foreach (string name in valuesDict.Names)
                dictionary.Add(getKey(name), DeSerialize(versionTimestamp, (BsonDocument)valuesDict[name], StreamSeeds, propertyPath + ""["" + name + ""]""));
            return dictionary;
        }}

        public static CIP4ReferenceType<{1}.{0}> MakeCIP4Reference(object value, CIP4ReferenceType<{1}.{0}> defaultValue)
        {{
            string externalID = GetExternalIdFromReferenceDoc((BsonDocument)value);
            if (!string.IsNullOrEmpty(externalID))
                return new CIP4ReferenceType<{1}.{0}> {{ TargetExternalID = externalID }};
            else
                return defaultValue;
        }}

        internal static List<CIP4ReferenceType<{1}.{0}>> DeSerializeReferenceList(BsonArray doc)
        {{
            List<CIP4ReferenceType<{1}.{0}>> list = new List<CIP4ReferenceType<{1}.{0}>>();
            if (doc == null)
                return list;
            foreach (object value in doc)
            {{
                object objectValue = Activator.CreateInstance(typeof(CIP4ReferenceType<{1}.{0}>), new object[] {{ }});
                ((CIP4ReferenceBaseType)objectValue).TargetExternalID = ((BsonDocument)value)[""TargetExternalID""].ToString();
                list.Add((CIP4ReferenceType<{1}.{0}>)objectValue);
            }}
            return list;
        }}

        public static BsonDocument SerializeItem({1}.{0} value, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, ISerializerType serializerWrapper)
        {{
{8}
{21}
            
            BsonDocument doc = new BsonDocument();
            string valueName = ""{22}"";
            doc.Add(valueName, Serialize(value, StreamSeeds, valueName, serializerWrapper));{5}
            return doc;
        }}

        public static BsonDocument SerializeValue({1}.{0} value, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, ISerializerType serializerWrapper)
        {{
{9}
            BsonDocument doc = new BsonDocument();
            string valueName = ""{22}"";
            doc.Add(valueName, Serialize(value, StreamSeeds, propertyPath + ""."" + valueName, serializerWrapper));
            return doc;
        }}

        internal static BsonDocument Serialize({1}.{0} value, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, ISerializerType serializerWrapper)
        {{
            BsonDocument propDoc = new BsonDocument();
{3}
            return propDoc;
        }}
{19}
    }}

";
        /// <summary>
        /// This is a Mongo artifact - may be a problem if the json serializer is used in a non-Mongo context
        /// </summary>
        private const string CSharpExternalIDLiteral =
@"
            doc.Add(""_id"", GetExternalIDForObject(value).ToString());";

        private const string CSharpExtensionMethodsLiteral =
@"    internal static class {0}ExtensionMethods
    {{
        public static {1}.{0} SetPrivateProperty(this {1}.{0} aThis, string propertyName, object aValue)
        {{
            Type aType = aThis.GetType();

            System.Reflection.PropertyInfo propInfo = aType.GetProperty(propertyName);

            if (propInfo == null)
                throw new ArgumentException(string.Format(""Property {{0}} is not available in type {{1}}"", propertyName, aType.Name));

            System.Reflection.MethodInfo setMethod = propInfo.GetSetMethod(true);

            if (setMethod == null)
                throw new ArgumentException(string.Format(""Property {{0}} in type {{1}} does not have a public, private, internal or protected set method"", propertyName, aType.Name));

            setMethod.Invoke(aThis, new object[] {{ aValue }});

            return aThis;
        }}
    }}
";



        private string SerializeHeader(Dictionary<string, TypeContextType> alreadyDone)
        {
            //_logger(MajorEnum.Trace, MinorEnum.Generic, "SerializeHeader for {0}",
            //        new object[] {TheType.TheTypeFullName});


            if (string.IsNullOrEmpty(FirstNamespaceName))
                FirstNamespaceName = TheType.TheTypeNamespace.Replace(".", "_");

            SerializerBaseTypeBodyCase(TheType);

            StringBuilder setPrivates = new StringBuilder();

            StringBuilder setVersionPrivates = new StringBuilder();

            StringBuilder deSerializeSeedStream = new StringBuilder();

            string deSerialzePropertiesText = DeSerializeProperties(setPrivates, deSerializeSeedStream);

            string deSerialzeVersionPropertiesText = DeSerializeVersionProperties(setVersionPrivates);

            string serializePropertiesText = Serializeproperties();

            string newItem = TheType.TheType.IsAbstract ?
                "            object objectValue = null;" :
                string.Format(CSharpNewItemLiteral,
                    TheType.TheTypeFullName,
                    deSerialzePropertiesText,
                    setPrivates, 
                    TheType.HasExternalID? CSharpExtactExternalIDLiteral : string.Empty);

            string versionItem = TheType.TheType.IsAbstract ?
                "            object objectValue = null;" :
                string.Format(CSharpNewItemLiteral,
                    TheType.TheTypeFullName,
                    deSerialzeVersionPropertiesText,
                    setVersionPrivates,
                    string.Empty);  // ToDo: will have to deal with this at some point - how to get the externalid from a version where the externalid is not stored - maybe it's not a problem - externalids are really write-once

// ToDo: check these and eliminate unused arguments
// ReSharper disable FormatStringProblem
            string text = string.Format(CSharpHeaderItemLiteral,
                TheType.TheTypeName,
                TheType.TheTypeNamespace,
                deSerialzePropertiesText,
                serializePropertiesText,
                string.Empty,
                GetExternalID(),
                deSerializeSeedStream,
                DeSerializeSubtypes(),
                SerializeSubtypes(),
                SerializeSubtypes2(),
                newItem,
                TheType.TypeSerializerName,
                MakeExternalIDGets(),
                MakeGetValue(),
                MakeKeys(),
                MakeCheckVersion(),
                DeSerializeVersionHandling(),
                versionItem,
                GetItemVersionBody(),
                GetEqualityComparer(),
                FirstNamespaceName,
                GetSerializeIncrementVersionNumber(),
                TheType.SerializeAs, 
                GetSerializeAsNameText());

// ReSharper restore FormatStringProblem
            if (setPrivates.Length > 0)
                text += string.Format(CSharpExtensionMethodsLiteral,
                    TheType.TheTypeName,
                    TheType.TheTypeNamespace);
            if (!alreadyDone.ContainsKey(TheType.TheTypeFullName))
                alreadyDone.Add(TheType.TheTypeFullName, TheType);
            foreach (TypeContextType usedType in UsedTypes.Values)
            {
                if (alreadyDone.ContainsKey(usedType.TheTypeFullName))
                    continue;
                text += new JsonSerializerBuilderType(usedType, _logger).SerializeHeader(alreadyDone);
            }
            if (TheType.TheTypeSpecificName == "CIP4ScalablePropertyType")
            {
                // What we want is a ChunkType with its generic parameter type being the same as the
                // generic parameter type used for the CIP4ScalablePropertyTYpe. We generate a serializer 
                // for the parameterized ChunkType - the code generated above by the JsonSerializerBuilderType
                // is hopefully smart enough to call it where appropriate.
                Type anOriginal = typeof(ChunkType<>).MakeGenericType(TheType.TheOtherType);
                TypeContextType usedType = new TypeContextType { OriginalType = anOriginal };
                if (!alreadyDone.ContainsKey(usedType.TheTypeFullName))                    text += new JsonSerializerBuilderType(usedType, _logger).SerializeHeader(alreadyDone);
            }
            return text;
        }

        #region externalid handling

        private const string CSharpGetExternalIDLiteral =
            // {0} - list of property name assignments, 
            // {1} - list of object property name assignements
            // {2} - fullname of the type
@"        public static ExternalIDType GetExternalID(BsonDocument propDoc)
        {{
            List<object> parts = new List<object>();
{0}
            return ExternalIDType.ExternalIDFromParts(parts);
        }}
        public static ExternalIDType GetExternalIDForObject({2} value)
        {{
            List<object> parts = new List<object>();
{1}
            return ExternalIDType.ExternalIDFromParts(parts);
        }}
";

        private string MakeExternalIDGets()
        {
            StringBuilder dataGets = new StringBuilder();
            
            StringBuilder objectGets = new StringBuilder();

            foreach (PropertyInfo prop in GetExternalIdProperties(TheType))
            {
                BuildExternalIdPaths(prop, string.Empty, string.Empty, dataGets, objectGets);
            }

            string result = string.Format(CSharpGetExternalIDLiteral,
                dataGets,
                objectGets,
                TheType.TheTypeFullName);

            return result;

        }

        private const string CSharpGetExternalIDFromJsonLiteral =  // {0} is the property name
            @"            parts.Add(StringValue(propDoc{0}, string.Empty));
";

        private const string CSharpGetExternalIDFromObjectLiteral =  // {0} is the property name
            @"            parts.Add(value.{0});
";

        private const string CSharpGetExternalIDPathLiteral =
            @"[""{0}""]";

        private void BuildExternalIdPaths(PropertyInfo prop, string qualifier, string path, StringBuilder dataGets, StringBuilder objectGets)
        {
            TypeContextType propType = new TypeContextType {OriginalType = prop.PropertyType};

            if (ValueType(propType.TheType))
            {
                dataGets.Append(String.Format(CSharpGetExternalIDFromJsonLiteral, path + string.Format(CSharpGetExternalIDPathLiteral, prop.Name)));
                objectGets.Append(string.Format(CSharpGetExternalIDFromObjectLiteral, qualifier + prop.Name));
            }
            else
            {
                foreach (PropertyInfo property in GetExternalIdProperties(propType))
                {
                    if (!IncludeThisProperty(property))
                        continue;
                    BuildExternalIdPaths(
                        property, 
                        qualifier + prop.Name + ".",
                        string.Format(CSharpGetExternalIDPathLiteral, prop.Name) + ".AsBsonDocument" +
                        string.Format(CSharpGetExternalIDPathLiteral, propType.TheTypeName) + ".AsBsonDocument",
                        dataGets, 
                        objectGets);
                }
            }
        }


        #endregion


        #region Deserialize

        private const string CSharpEqualityComparerWithExternalIDLiteral =
@"
        public class EqualityComparer : IEqualityComparer<{0}>
        {{
            public bool Equals({0} x, {0} y)
            {{
                return GetExternalIDForObject(x).ToString() == GetExternalIDForObject(y).ToString();
            }}

            public int GetHashCode({0} obj)
            {{
                return GetExternalIDForObject(obj).ToString().GetHashCode();
            }}
        }}
";

        private const string CSharpEqualityComparerWithOutExternalIDLiteral =
@"
        public class EqualityComparer : IEqualityComparer<{0}>
        {{
            public bool Equals({0} x, {0} y)
            {{
                return SerializeValue(x, null, null, null).ToJson() == SerializeValue(y, null, null, null).ToJson();
            }}

            public int GetHashCode({0} obj)
            {{
                return SerializeValue(obj, null, null, null).ToJson().GetHashCode();
            }}
        }}
";

        protected string GetEqualityComparer()
        {
            if (TheType.HasExternalID)
                return string.Format(CSharpEqualityComparerWithExternalIDLiteral, TheType.TheTypeFullName);

            return string.Format(CSharpEqualityComparerWithOutExternalIDLiteral, TheType.TheTypeFullName);
        }

        // {0} - property name
        // {1} - type full name
        private const string CSharpVersionHandlingLiteral =
@"            // Cannot just reference the item that just got deserialized, as we must have the before 
            // image in order to be able to calculate the diffs, hence the need for a completely new copy  
            // Versioning may be priceless - it certainly ain't cheap
            CIP4ItemVersionsType versions = new CIP4ItemVersionsType(versionTimestamp);

            if (propDoc.Contains(""{0}""))
            {{
                foreach (BsonDocument vDoc  in (BsonArray)propDoc[""{0}""])
                {{
                    Dictionary<string, object> values = new Dictionary<string, object>();
                    string version = string.Empty;
                    foreach (string name in vDoc.Names)
                    {{
                        if (name == ""_version"")
                            version = vDoc[name].ToString();
                        else
                            values.Add(name, ValueFromJson(vDoc[name], DeSerializeFromJson));
                    }}
                    versions.AddVersion(version, values);
                }}
            }}

            versions.SetCurrent<{1}>(SetProperties(versionTimestamp, docData, propDoc, null, propertyPath));

            versions.GetCurrent<{1}>().{0} = versions;

            objectValue.{0} = versions;

";

        private string DeSerializeVersionHandling()
        {
            PropertyInfo versionsProp = VersionProperty;
            if (versionsProp != null)
            {
                Type versionsPropType = TheType.TheType;
                if (!(versionsPropType == TheType.TheType || TheType.TheType.IsSubclassOf(versionsPropType)))
                    throw new ArgumentException(string.Format("The version property {0} for {1} must have a {1} as its generic parameter type ",
                        versionsProp.Name, TheType));
                return string.Format(CSharpVersionHandlingLiteral,
                    versionsProp.Name,
                    TheType.TheTypeFullName);
            }
            return "            // no version handling for this type";
        }

        private const string CSharpDeSubtypesLiteral =
@"            if (docData.Any(kvp=>kvp.Name == ""{0}""))
                return {1}.DeSerialize(versionTimestamp, docData, StreamSeeds, propertyPath);
";

        private const string CSharpCheckElementLiteral =
@"            if (!docData.Any(kvp=>kvp.Name == ""{0}""))
                return null;
";

        private string DeSerializeSubtypes()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpDeSubtypesLiteral,
                    tc.TheTypeName,
                    tc.TypeSerializerName);
                if (!UsedTypes.ContainsKey(tc.TheTypeFullName))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
            }
            if (!TheType.TheTypeName.Contains("<"))  // this is a hack to cater for the way that scalable properties deal with type - basically scalable properties use the wrong type name at times
                text += string.Format(CSharpCheckElementLiteral,
                    TheType.SerializeAs);
            return text;
        }

        private string DeSerializeProperties(StringBuilder setPrivates, StringBuilder deSerializeSeedStream)
        {
            string text = string.Empty;
            List<PropertyInfo> externalIds = GetExternalIdProperties(TheType);
            foreach (PropertyInfo property in TheType.TheType.GetProperties())
            {
                //_logger(MajorEnum.Trace, MinorEnum.Generic, 
                //    "DeSerializeProperties - Generating set property for Key IncludeThisProperty={0}, externalIds.Contains={1}", 
                //    new object[] { IncludeThisProperty(property), externalIds.Contains(property)});

                if (!IncludeThisProperty(property) && !externalIds.Contains(property))
                    continue;
                if (property.GetSetMethod(true) == null)
                    continue;
                if (property.PropertyType.GetInterface("IList`1") != null || property.PropertyType.Name == "HashSet`1")
                    text += DeserializeMV(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += DeserializeMVDictionary(property);
                else
                    text += DeserializeSV(property, setPrivates, deSerializeSeedStream);
            }
            return text;
        }

        // 0 - version property name
        private const string CSharpPerpetuateVersionsLiteral =
            @"
            objectValue.{0} = previousVersion.{0};
";

        private string DeSerializeVersionProperties(StringBuilder setPrivates)
        {

            string text = string.Empty;
            List<PropertyInfo> externalIds = GetExternalIdProperties(TheType);
            foreach (PropertyInfo property in TheType.TheType.GetProperties())
            {
                if (!IncludeThisProperty(property) && externalIds.Contains(property))
                    continue;
                if (property.GetSetMethod(true) == null)
                    continue;
                if (!VersionThisType(property.PropertyType))
                    continue;
                if (property.PropertyType.GetInterface("IList`1") != null || property.PropertyType.Name == "HashSet`1")
                    text += DeserializeMVVersion(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += DeserializeDictionaryVersion(property);
                else
                    text += DeserializeSVVersion(property, setPrivates);
            }

            // perpetuating versions maybe not such a great idea - to do it properly would have to truncate the version info - which sounds hard
            if (VersionProperty != null)
                text += string.Format(CSharpPerpetuateVersionsLiteral,
                    VersionProperty.Name);

            return text;
        }


        private const string CSharpSvNullableVersionLiteral =
            @"
            if (Values.ContainsKey(""{2}""))
                objectValue.{0} = {1}Value(Values[""{2}""], objectValue.{0});
            else 
                objectValue.{0} = previousVersion.{0};";

        private const string CSharpSvVersionLiteral =
            @"
            if (Values.ContainsKey(""{2}""))
                objectValue.{0} = {1}Value(Values[""{2}""], objectValue.{0});
            else 
                objectValue.{0} = previousVersion.{0};";

        private const string CSharpDeEnumVersionLiteral =
            @"
            if (Values.ContainsKey(""{2}""))
                objectValue.{0} = ({1})GetEnum(Values[""{2}""].ToString(), typeof({1}), objectValue.{0});
            else 
                objectValue.{0} = previousVersion.{0};";

        private const string CSharpCompoundSvVersionLiteral =
            @"
            if (Values.ContainsKey(""{2}""))
                objectValue.{0} = ({1})Values[""{2}""];
            else 
                objectValue.{0} = previousVersion.{0};
";

        // {0} - is the property name
        // {1} - is the property type
        // {2} - is the serializer
        // {3} - is the property types version property
        private const string CSharpVersionedCompoundSvVersionLiteral =
            @"
            if (Values.ContainsKey(""{4}""))
                objectValue.{0} = ({1})Values[""{4}""];
//            else if (previousVersion.{0} != null && previousVersion.{0}.{3} != null && previousVersion.{0}.{3}.Versions.ContainsKey(versionTimestamp))
//                objectValue.{0} = {2}.GetItemVersion(versionTimestamp, previousVersion.{0}, previousVersion.{0}.{3}.Versions[versionTimestamp].Values);
            else if (previousVersion.{0} != null && previousVersion.{0}.{3} != null)
                objectValue.{0} = previousVersion.{0}.{3}.GetItemVersion<{1}>(versionTimestamp, {2}.GetItemVersion);
            else if (previousVersion.{0} != null)
                objectValue.{0} = previousVersion.{0};
";

        private const string CSharpPrivatesVersionLiteral =
            @"
            if (Values.ContainsKey(""{2}""))
                objectValue.SetPrivateProperty(""{2}"", {1}Value(Values[""{2}""].ToString(), default({1})));
            else 
                objectValue.SetPrivateProperty(""{2}"", previousVersion.{0});

";

        
        private string DeserializeSVVersion(PropertyInfo property, StringBuilder setPrivates)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.TheType == typeof(Stream))
            {
                return string.Empty;
            }
            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                return string.Empty;
            }
            if (actualType.TheType.IsEnum)
            {
                return string.Format(CSharpDeEnumVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    GetSerializeAs(property));
            }
            if (ValueType(actualType.TheType))
            {
                if (property.GetSetMethod() == null)
                {
                    setPrivates.Append(string.Format(CSharpPrivatesVersionLiteral,
                        property.Name,
                        actualType.TheTypeName,
                        GetSerializeAs(property)));
                    return string.Empty;
                }
                if (actualType.HostTypeName == "Nullable")
                    return string.Format(CSharpSvNullableVersionLiteral,
                        property.Name,
                        actualType.TheTypeName,
                        GetSerializeAs(property));
                return string.Format(CSharpSvVersionLiteral,
                    property.Name,
                    actualType.TheTypeName,
                    GetSerializeAs(property));
            }
            if (IncludeThisType(actualType.TheType))
            {
                if (actualType.VersionProperty != null)
                {
                    return string.Format(CSharpVersionedCompoundSvVersionLiteral,
                        property.Name,
                        actualType.TheTypeFullName,
                        actualType.TypeSerializerName,
                        actualType.VersionProperty.Name,
                        GetSerializeAs(property));
                }
                return string.Format(CSharpCompoundSvVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        private const string CSharpMvSimpleVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1}{2}>(""{3}"", Values, previousVersion.{0});
";

        private const string CSharpMvSimpleArrayVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1}{2}>(""{3}"", Values, previousVersion.{0}).ToArray();
";

        private const string CSharpMvSimpleHashSetVersionLiteral =
            @"
            objectValue.{0} = new HashSet<{1}>(MvVersion <{1}>(""{2}"", Values, previousVersion.{0}.ToList()));
";

        private const string CSharpMvCompoundVersionLiteral =
            @"
            objectValue.{0} = MvVersion<{1}>(""{2}"", Values, previousVersion.{0});
";

        private string DeserializeMVVersion(PropertyInfo property)
        {
            if (property.PropertyType.GetInterface("IList`1") == null &&
                !property.PropertyType.IsArray &&
                property.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };

            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (ValueType(property.PropertyType))
            {
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpMvSimpleArrayVersionLiteral,
                        property.Name,
                        actualType.TheTypeFullName,
                        actualType.IsNullable ? "?" : string.Empty,
                        GetSerializeAs(property));
                if (property.PropertyType.Name == "HashSet`1")
                    return string.Format(CSharpMvSimpleHashSetVersionLiteral,
                        property.Name,
                        actualType.TheTypeSpecificName,
                        GetSerializeAs(property));
                return string.Format(CSharpMvSimpleVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    actualType.IsNullable ? "?" : string.Empty,
                    GetSerializeAs(property));
            }
            if (IncludeThisType(actualType.TheType))
            {
                return string.Format(CSharpMvCompoundVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        private const string CSharpDictionarySimpleVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1},{2}{3}>(""{4}"", Values, previousVersion.{0});
";

        private const string CSharpDictionaryCompoundVersionLiteral =
            @"
            objectValue.{0} = MvVersion<{1},{2}>(""{3}"", Values, previousVersion.{0});
";

        private string DeserializeDictionaryVersion(PropertyInfo property)
        {
            if (!property.PropertyType.IsGenericType)
                return string.Empty;

            TypeContextType valueType = new TypeContextType { OriginalType = property.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = property.PropertyType.GetGenericArguments()[0] };

            if (valueType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (ValueType(valueType.TheType))
            {
                return string.Format(CSharpDictionarySimpleVersionLiteral,
                    property.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    valueType.IsNullable ? "?" : string.Empty,
                    GetSerializeAs(property));
            }
            if (IncludeThisType(valueType.TheType))
            {
                return string.Format(CSharpDictionaryCompoundVersionLiteral,
                    property.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        
        
        private const string CSharpDeSimplePrivateLiteral = // {0} property name  - {1} property type
@"
            objectValue.SetPrivateProperty(""{0}"", {1}Value(GetValueFromDoc(""{2}"", propDoc), default({1})));";

        private const string CSharpDeSimpleLiteral =
@"                objectValue.{0} = {1}Value(GetValueFromDoc(""{2}"", propDoc), objectValue.{0});
";

        private const string CSharpDeSimpleExternalIdLiteral =
@"                objectValue.{0} = {1}Value(id.GetPart({2}), objectValue.{0});
";

        private const string CSharpDeEnumExternalIdLiteral =
@"                objectValue.{0} = ({1})GetEnum(id.GetPart({2}).ToString(), typeof({1}), objectValue.{0});
";

        private const string CSharpDeEnumLiteral =
@"                objectValue.{0} = ({1})GetEnum(GetStringValueFromDoc(""{2}"", propDoc), typeof({1}), objectValue.{0});
";

        private const string CSharpDeCompoundLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerialize(versionTimestamp, propDoc.Contains(""{2}"")? (BsonDocument)propDoc[""{2}""] : default(BsonDocument), StreamSeeds, propertyPath + "".{0}"", objectValue.{0});
";

        private const string CSharpDeReferenceLiteral =
@"                objectValue.{0} = {1}.MakeCIP4Reference(propDoc.Contains(""{2}"")? propDoc[""{2}""] : null, objectValue.{0});

";

        private const string CSharpDeStreamLiteral =
@"            if (StreamSeeds != null && propDoc.Contains(""{1}""))
                StreamSeeds.Add(propertyPath + "".{1}"", new KeyValuePair<PropertyInfo, object> (typeof({0}).GetProperty(""{1}""), objectValue));
";

        private string DeserializeSV(PropertyInfo property, StringBuilder setPrivates, StringBuilder deSerializeSeedStream)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };

            CIP4PropertyAttribute patt = CIP4PropertyAttribute.GetAttribute(property);

            CIP4ExternalIDAttribute eatt = CIP4ExternalIDAttribute.GetAttribute(property);

            if (actualType.TheType == typeof(Stream))
            {
                deSerializeSeedStream.Append(string.Format(CSharpDeStreamLiteral,
                    GetActualType(property.DeclaringType).FullName,
                    GetSerializeAs(property)));
            }
            else if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Format(CSharpDeReferenceLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            else if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                return string.Format(CSharpDeCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            else if (actualType.TheType.IsEnum)
            {
                if (patt != null && patt.DontSerialize && eatt != null)
                {
                    return string.Format(CSharpDeEnumExternalIdLiteral,
                        property.Name,
                        actualType.TheTypeFullName,
                        eatt.Rank - 1);
                }
                return string.Format(CSharpDeEnumLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    GetSerializeAs(property));
            }
            else if (ValueType(actualType.TheType))
            {
                //_logger(MajorEnum.Trace, MinorEnum.Generic, "Generating set property for Key patt={0}, eatt={1}", new object[] { patt, eatt});
                
                if (patt != null && patt.DontSerialize && eatt != null)
                {
                    return string.Format(CSharpDeSimpleExternalIdLiteral,
                        property.Name,
                        actualType.TheTypeName,
                        eatt.Rank - 1);
                }
                if (property.GetSetMethod() == null)
                {
                    setPrivates.Append(string.Format(CSharpDeSimplePrivateLiteral,
                        property.Name,
                        actualType.TheTypeName,
                        GetSerializeAs(property)));
                    return string.Empty;
                }
                return string.Format(CSharpDeSimpleLiteral,
                    property.Name,
                    actualType.TheTypeName,
                    GetSerializeAs(property));
            }
            else if (IncludeThisType(actualType.TheType))
            {
                return string.Format(CSharpDeCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        private const string CSharpDeMvCompoundLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerializeList(versionTimestamp, GetValueFromDoc(""{2}"", propDoc), StreamSeeds, propertyPath + "".{2}"", objectValue.{0});
";

        private const string CSharpDeMvArrayCompoundLiteral =
@"                object {0}Values = GetValueFromDoc(""{2}"", propDoc);
                if ({0}Values != null)
                    objectValue.{0} = CIP4.Serializer.{1}.DeSerializeList(versionTimestamp, GetValueFromDoc(""{2}"", propDoc), StreamSeeds, propertyPath + "".{2}"", null).ToArray();
";

        private const string CSharpDeMvSimpleLiteral =
@"                objectValue.{0} = ListValues<{1}{2}>(GetValueFromDoc(""{3}"", propDoc));
";

        private const string CSharpDeMvSimpleHashSetLiteral =
@"                objectValue.{0} = HashSetValues<{1}>(GetValueFromDoc(""{2}"", propDoc));
";

        private const string CSharpDeMvArraySimpleLiteral =
@"                objectValue.{0} = ListValues<{1}{2}>(GetValueFromDoc(""{3}"", propDoc)).ToArray();
";

        private const string CSharpDeMvReferenceLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerializeReferenceList((BsonArray)GetValueFromDoc(""{2}"", propDoc));
";

        private string DeserializeMV(PropertyInfo property)
        {
            if (property.PropertyType.GetInterface("IList`1") == null &&
                !property.PropertyType.IsArray &&
                property.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };

            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Format(CSharpDeMvReferenceLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            if (ValueType(property.PropertyType))
            {
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpDeMvArraySimpleLiteral,
                        property.Name,
                        actualType.TheTypeFullName,
                        actualType.IsNullable ? "?" : string.Empty,
                        GetSerializeAs(property));
                if (property.PropertyType.Name == "HashSet`1")
                    return string.Format(CSharpDeMvSimpleHashSetLiteral,
                        property.Name,
                        actualType.TheTypeSpecificName,
                        GetSerializeAs(property));
                return string.Format(CSharpDeMvSimpleLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    actualType.IsNullable ? "?" : string.Empty,
                    GetSerializeAs(property));
            }
            if (IncludeThisType(actualType.TheType))
            {
                if (!UsedTypes.ContainsKey(actualType.TheTypeFullName))
                    UsedTypes.Add(actualType.TheTypeFullName, actualType);
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpDeMvArrayCompoundLiteral,
                        property.Name,
                        actualType.TypeSerializerName,
                        GetSerializeAs(property));
                return string.Format(CSharpDeMvCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }


        private const string CSharpDeMvDictCompoundLiteral =  // {0} - property name {1} qualified property type name {2} unqualified property type name {3} keytype
@"                objectValue.{0} = (Dictionary<{3}, {1}>)
                    CIP4.Serializer.{2}.DeSerializeDictionary(
                        versionTimestamp, 
                        GetValueFromDoc(""{5}"", propDoc),
                        delegate() {{ return new Dictionary<{3}, {1}>(); }},
                        delegate(string keyValue) {{ return {4}Value(keyValue, default({3}));}},
                        StreamSeeds, propertyPath + "".{5}"", objectValue.{0});
";

        private const string CSharpDeMvDictSimpleLiteral = // {0} property name {1} property value type {2} key type
@"                objectValue.{0} = (Dictionary<{2}, {1}{4}>)
                    GetObjectDictionary(
                        GetValueFromDoc(""{5}"", propDoc),
                        typeof({1}{4}),
                        delegate() {{ return new Dictionary<{2}, {1}{4}>(); }},
                        delegate(string keyValue) {{ return {3}Value(keyValue, default({2})); }}, 
                        objectValue.{0});
";

        private string DeserializeMVDictionary(PropertyInfo property)
        {
            if (!property.PropertyType.IsGenericType)
                return string.Empty;
            TypeContextType valueType = new TypeContextType { OriginalType = property.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = property.PropertyType.GetGenericArguments()[0] };
            if (valueType.HostTypeName == "CIP4ReferenceType")
                throw new NotImplementedException("Generated serializer doesn't support dictionaries of references at this time");
            if (ValueType(valueType.TheType))
                return string.Format(CSharpDeMvDictSimpleLiteral,
                    property.Name,
                    valueType.TheTypeFullName,
                    keyType.TheTypeFullName,
                    keyType.TheTypeName,
                    valueType.IsNullable ? "?" : string.Empty,
                    GetSerializeAs(property));
            if (IncludeThisType(valueType.TheType))
            {
                if (!UsedTypes.ContainsKey(valueType.TheTypeFullName))
                    UsedTypes.Add(valueType.TheTypeFullName, valueType);
                // {0} - property name {1} qualified property type name {2} unqualified property type name {3} keytype
                return string.Format(CSharpDeMvDictCompoundLiteral,
                    property.Name,
                    valueType.TheTypeFullName,
                    valueType.TypeSerializerName,
                    keyType.TheTypeFullName,
                    keyType.TheTypeName,
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        #endregion

        #region Serialize

        private const string CSharpSubtypesLiteral =
@"            if (value is {0})
                return {1}.SerializeItem(({0})value, StreamSeeds, serializerWrapper);
";

        private string SerializeSubtypes()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpSubtypesLiteral,
                    tc.TheTypeFullName,
                    tc.TypeSerializerName);
            }
            return text;
        }

        private const string CSharpSubtypes2Literal =
@"            if (value is {0})
            {{
                return {1}.SerializeValue(({0})value, StreamSeeds, propertyPath, serializerWrapper);
            }}
";

        private string SerializeSubtypes2()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpSubtypes2Literal,
                    tc.TheTypeFullName,
                    tc.TypeSerializerName);
            }
            return text;
        }

        private string GetExternalID()
        {
            if (GetExternalIdProperties(TheType).Count > 0)
                return CSharpExternalIDLiteral;
            return string.Empty;
        }

        private const string CSharpVersionsPropertySerializeLiteral =
@"            if (value.{0} != null && value.{0}.Versions.Count > 0)
                JsonSerializeVersion(value.{0}, propDoc, SerializeToJson);

";

        private string Serializeproperties()
        {
            string text = string.Empty;
            List<PropertyInfo> externalIds = GetExternalIdProperties(TheType);
            foreach (PropertyInfo property in WrapperSerializerType.SortedProperties(TheType.TheType.GetProperties()).Values)
            {
                if (!IncludeThisProperty(property) && externalIds.Contains(property))
                    continue;
                if (property.PropertyType.GetInterface("IList") != null || property.PropertyType.Name == "HashSet`1")
                    text += SerializeMV(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += SerializeMVDictionary(property);
                else
                    text += SerializeSV(property);
            }
            if (VersionProperty != null)
                text += string.Format(CSharpVersionsPropertySerializeLiteral,
                    VersionProperty.Name);
            return text;
        }

        private const string CSharpDictionaryVersionsLiteral =
@"
                if (value.Versions != null)
                    value.Versions.MakeDictionaryDelta(""{3}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeDictionaryVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpDictionaryVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                GetSerializeAs(property));
        }

        private const string CSharpDictionaryNowNullVersionsLiteral =
@"
                else if (value.Versions != null)
                    value.Versions.MakeDictionaryDelta(""{3}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeDictionaryNowNullVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpDictionaryNowNullVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                GetSerializeAs(property));
        }

        private const string CSharpMvVersionsLiteral =
@"
                if (value.Versions != null)
                    value.Versions.MakeMvDelta(""{3}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeMVVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpMvVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                GetSerializeAs(property));
        }

        private const string CSharpMvNowNullVersionsLiteral =
@"
                else if (value.Versions != null)
                    value.Versions.MakeMvDelta(""{3}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeMVNowNullVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpMvNowNullVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                GetSerializeAs(property));
        }

        private const string CSharpMvCompoundSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                BsonArray values = new BsonArray();
                int index = 0;
                foreach ({2} propertyValue in value.{0})
                    values.Add({1}.SerializeValue(propertyValue, StreamSeeds, propertyPath + "".{5}["" + (++index).ToString() + ""]"", serializerWrapper));
                propDoc.Add(""{5}"", values);{3}
            }}{4}
";

        private const string CSharpMvSimpleSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                BsonArray values = new BsonArray();
                foreach ({1} propertyValue in value.{0})
                    values.Add(BsonValueFromObject(typeof({1}).Name, propertyValue));
                propDoc.Add(""{4}"", values);{2}
            }}{3}
";

        private const string CSharpMvReferenceSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                BsonArray values = new BsonArray();
                foreach (CIP4ReferenceType<{1}> propertyValue in value.{0})
                {{
                    values.Add(new BsonDocument(""TargetExternalID"", propertyValue.TargetExternalID));
                }}
                propDoc.Add(""{2}"", values);
            }}
";

        private string SerializeMV(PropertyInfo prop)
        {
            if (prop.PropertyType.GetInterface("IList") == null && !prop.PropertyType.IsArray && prop.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = prop.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                TypeContextType hostedType = new TypeContextType { OriginalType = actualType.TheType };
                foreach (TypeContextType tc in (TargetTypes(hostedType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpMvReferenceSerializeLiteral,
                    prop.Name,
                    actualType.TheType.FullName,
                    GetSerializeAs(prop));
            }
            if (ValueType(actualType.TheType))
            {
                return string.Format(CSharpMvSimpleSerializeLiteral,
                    prop.Name,
                    actualType.TheTypeFullName,
                    SerializeMVVersions(prop, actualType),
                    SerializeMVNowNullVersions(prop, actualType),
                    GetSerializeAs(prop));
            }
            if (IncludeThisType(actualType.TheType))
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpMvCompoundSerializeLiteral,
                     prop.Name,
                     actualType.TypeSerializerName,
                     actualType.TheTypeFullName,
                     SerializeMVVersions(prop, actualType),
                     SerializeMVNowNullVersions(prop, actualType),
                    GetSerializeAs(prop));
            }
            return string.Empty;
        }

        const string CSharpMvDictCompoundSerializeLiteral =  // {0} property name  // {1} key type {2} qualified valuetype name {3} valuetype name
@"            if (value.{0} != null && value.{0}.Count > 0)
            {{
                BsonDocument dictDoc = new BsonDocument();
                foreach (KeyValuePair<{1}, {2}> propertyValue in value.{0})
                    dictDoc.Add(propertyValue.Key.ToString(), {3}.SerializeValue(propertyValue.Value, StreamSeeds, propertyPath + "".{6}["" +  propertyValue.Key.ToString() + ""]"", serializerWrapper));
                propDoc.Add(""{6}"", dictDoc);{4}
            }}{5}
";

        private const string CSharpMvDictSimpleSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count > 0)
            {{
                BsonDocument dictDoc = new BsonDocument();
                foreach (KeyValuePair<{1}, {2}{5}> propertyValue in value.{0})
                    dictDoc.Add(propertyValue.Key.ToString(), BsonValueFromObject(typeof({2}).Name, propertyValue.Value));
                propDoc.Add(""{6}"", dictDoc);{3}
            }}{4}
";
        private string SerializeMVDictionary(PropertyInfo prop)
        {
            if (!prop.PropertyType.IsGenericType)
                return string.Empty;
            TypeContextType valueType = new TypeContextType { OriginalType = prop.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = prop.PropertyType.GetGenericArguments()[0] };

            if (valueType.HostTypeName == "CIP4ReferenceType")
                throw new NotImplementedException("Dictionaries of references are not implemented yet");
            if (ValueType(valueType.TheType))
                return string.Format(CSharpMvDictSimpleSerializeLiteral,
                    prop.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    SerializeDictionaryVersions(prop, valueType),
                    SerializeDictionaryNowNullVersions(prop, valueType),
                    valueType.IsNullable ? "?" : string.Empty,
                    GetSerializeAs(prop));
            if (IncludeThisType(valueType.TheType))
            {
                foreach (TypeContextType tc in (TargetTypes(valueType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpMvDictCompoundSerializeLiteral,
                    prop.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    valueType.TypeSerializerName,
                    SerializeDictionaryVersions(prop, valueType),
                    SerializeDictionaryNowNullVersions(prop, valueType),
                    GetSerializeAs(prop));
            }
            return string.Empty;
        }

        private const string CSharpStreamTypeSerializeLiteral =
@"            if (value.{0} != null)
            {{
                propDoc.Add(""{2}"", propertyPath + "".{2}"");
                if (StreamSeeds != null)
                    StreamSeeds.Add(propertyPath + "".{2}"", new KeyValuePair<PropertyInfo, object> (typeof({1}).GetProperty(""{0}""), value.{0}));
            }}
";

        private string SerializeSV(PropertyInfo property)
        {
            if (property.PropertyType == typeof(Stream))
                return string.Format(CSharpStreamTypeSerializeLiteral, 
                    property.Name,
                    GetActualType(property.DeclaringType).FullName,
                    GetSerializeAs(property));
            
            if (ValueType(property.PropertyType))
                return SerializeSimple(property);
            
            return SerializeCompound(property);
        }

        // {0} - property name
        // {1} - property type serializer name
        // {2} - version handling where property is null
        // {3} - version handling where property was null
        private const string CSharpCompoundLiteral =
@"            if (value.{0} != null)
            {{
                propDoc.Add(""{4}"", {1}.SerializeValue(value.{0}, StreamSeeds, propertyPath + "".{4}"", serializerWrapper));{3}
            }}{2}
";

        private const string CSharpScalableLiteral =
@"            if (value.{0} != null)
            {{
                propDoc.Add(""{2}"", {1}.SerializeValue(value.{0}, StreamSeeds, propertyPath + "".{2}"", serializerWrapper));
                value.{0}.Flush (serializerWrapper);
            }}
";

        private string SerializeCompound(PropertyInfo property)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                TypeContextType hostedType = new TypeContextType { OriginalType = actualType.TheType };
                foreach (TypeContextType tc in (TargetTypes(hostedType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);

                return string.Format(CSharpReferenceTypeSerializeLiteral,
                    property.Name,
                    SerializeSimpleVersions(property),
                    GetSerializeAs(property));
            }
            if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                {
                    if (!UsedTypes.ContainsKey(tc.TheTypeFullName))
                        UsedTypes.Add(tc.TheTypeFullName, tc);
                }
                return string.Format(CSharpScalableLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    GetSerializeAs(property));
            }
            if (IncludeThisType(property.PropertyType))
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    SerializeCompoundVersions(property),
                    CompoundVersionsWasNull(property),
                    GetSerializeAs(property));
            }
            return string.Empty;
        }

        // {0} - qualified name of property owner
        // {1} - property name
        // {2} - serializer name for property type
        // {3} - property owner version property name
        private const string CSharpSerializeCompoundVersionsLiteral =
            @"
            else if (value.{2} != null && (({0})value.{2}.GetCurrent<{0}>()).{1} != null)
            {{
                value.{2}.AddValue(""{3}"", (({0})value.{2}.GetCurrent<{0}>()).{1});
            }}
";

        private string SerializeCompoundVersions(PropertyInfo property)
        {
            TypeContextType propertyType = new TypeContextType(property.PropertyType);

            if (VersionProperty == null || propertyType.VersionProperty == null)
                return string.Empty;

            return string.Format(CSharpSerializeCompoundVersionsLiteral,
                TheType.TheTypeFullName,
                property.Name,
                VersionProperty.Name,
                GetSerializeAs(property));
        }

        // {0} - qualified name of property owner
        // {1} - property owner version property name
        // {2} - property name
        private const string CSharpCompoundVersionsWasNullLiteral =
@"
                if (value.{1} != null && (({0})value.{1}.GetCurrent<{0}>()).{2} == null)
                    value.{1}.AddValue(""{3}"", null);
";

        private string CompoundVersionsWasNull(PropertyInfo property)
        {
            TypeContextType propertyType = new TypeContextType(property.PropertyType);

            if (VersionProperty == null || propertyType.VersionProperty == null)
                return string.Empty;

            return string.Format(CSharpCompoundVersionsWasNullLiteral,
                TheType.TheTypeFullName,
                VersionProperty.Name,
                property.Name, 
                GetSerializeAs(property));
        }

        private const string CSharpReferenceTypeSerializeLiteral =
@"            if (value.{0} != null && value.{0}.HasValue)
            {{
                propDoc.Add(""{2}"", new BsonDocument(""TargetExternalID"", ((CIP4ReferenceBaseType)value.{0}).TargetExternalID));{1}
            }}
";

        private const string CSharpValueTypeSerializeLiteral =
@"            if (value.{0} != default({2}))
            {{
                propDoc.Add(""{5}"", {1});{3}
            }}{4}
";

        private const string CSharpValueTypeSerializeEnumLiteral =
@"            if (value.{0} != default({1}))
            {{
                propDoc.Add(""{4}"", value.{0}.ToString());{2}
            }}{3}
";

        private const string CSharpNullableValueTypeSerializeLiteral =
@"            if (value.{0}.HasValue)
            {{
                propDoc.Add(""{3}"", value.{0});{1}
            }}{2}
";

        private const string CSharpNullableValueTypeSerializeEnumLiteral =
@"            if (value.{0}.HasValue)
            {{
                propDoc.Add(""{3}"", value.{0}.ToString());{1}
            }}{2}
";

        private string SerializeSimple(PropertyInfo property)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
                return string.Format(CSharpReferenceTypeSerializeLiteral,
                    property.Name,
                    SerializeSimpleVersions(property),
                    GetSerializeAs(property));
            if (actualType.TheType.IsEnum)
            {
                if (!IncludeThisProperty(property))
                    return string.Empty;
                if (actualType.HostTypeName == "Nullable")
                    return string.Format(CSharpNullableValueTypeSerializeEnumLiteral,
                        property.Name,
                        SerializeSimpleVersions(property),
                        SerializeSvNullVersion(property),
                        GetSerializeAs(property)) + Environment.NewLine;
                return string.Format(CSharpValueTypeSerializeEnumLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    SerializeSimpleVersions(property),
                    SerializeSvNullVersion(property),
                    GetSerializeAs(property)) + Environment.NewLine;
            }
            if (ValueType(actualType.TheType))
            {
                if (!IncludeThisProperty(property))
                    return string.Empty;
                if (actualType.HostTypeName == "Nullable")
                    return string.Format(CSharpNullableValueTypeSerializeLiteral,
                        property.Name,
                        SerializeSimpleVersions(property),
                        SerializeSvNullVersion(property),
                        GetSerializeAs(property)) + Environment.NewLine;
                return string.Format(CSharpValueTypeSerializeLiteral,
                    property.Name,
                    SerializeSimpleValue(property),
                    actualType.TheTypeFullName,
                    SerializeSimpleVersions(property),
                    SerializeSvNullVersion(property),
                    GetSerializeAs(property)) + Environment.NewLine;
            }
            return string.Empty;
        }

        // this should agree with BsonValueFromObject in SerializerBaseType
        private static string SerializeSimpleValue(PropertyInfo property)
        {
            Type actualType = GetActualType(property.PropertyType);
            TypeContextType contextType = new TypeContextType { OriginalType = property.PropertyType };
            string valueString = contextType.HostTypeName == "Nullable" ? ".Value" : string.Empty;
            if (actualType == typeof(string))
                return string.Format("value.{0}", property.Name);
            if (actualType == typeof(char))
                return string.Format("string.Format(\"0x{{0:X}}\", System.Convert.ToInt16(value.{0}))", property.Name + valueString);
            if (actualType == typeof(Single) || actualType == typeof(Double))
                return string.Format("DoubleConverter.ToExactString(value.{0})", property.Name + valueString);
            if (actualType.IsEnum)
                return string.Format("value.{0}.ToString()", property.Name + valueString);
            if (actualType.Name == "Color")
                return string.Format("RgbForColor(value.{0})", property.Name + valueString);
            if (actualType == typeof(DateTime))
                return string.Format("value.{0}.ToString(\"s\")", property.Name + valueString);
            if (actualType == typeof(DateTimeOffset))
                return string.Format("value.{0}.Ticks", property.Name + valueString);
            if (actualType == typeof(TimeSpan))
                return string.Format("XmlConvert.ToString(value.{0})", property.Name + valueString);
            if (actualType == typeof(decimal))
                return string.Format("value.{0}.ToString()", property.Name + valueString);
            if (actualType.IsValueType)
                return string.Format("value.{0}", property.Name);

            return string.Format("value.{0}.ToString()", property.Name);

        }
        #endregion
    }
}